package shopline

import (
	"context"
	"errors"
	"fmt"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gtime"
	"github.com/gogf/gf/v2/util/gconv"
	"net/url"
	"strings"
	"wxgf/library/easyHttp"
	"wxgf/library/easyResponse"
)

/*
Shopline平台对接
参考文档：https://developer.myshopline.com/docs/introduce/NOAkhsPT
*/

func Init(s *ghttp.Server) {
	initConfig()
	// 注入路由
	s.Group("/api/affiliates/shopline", func(group *ghttp.RouterGroup) {
		group.ALL("/webhooks", shoplineWebhook)
		group.ALL("/tokens", refreshToken)

		// ①  点击“去使用”，请求应用 【验签】
		// ** 该接口只有新授权才会被使用（虽然文档里面有，但是有界面双重嵌套问题，shopline说要等到9月份才会上线）
		// 确认登录后，跳转到应用地址，携带handle、sign等信息，需要验签，验签时建议取query路径上的所有参数（007步骤）
		group.ALL("/apps/{shopline_app_name}/authorizations", appAuthorize)
		// 授权后的回调接口
		group.ALL("/apps/{shopline_app_name}/callbacks", shoplineCallback)
	})
}

type merchantAppsOperateInfoChangedFunc func(r *ghttp.Request, op *MerchantAppsOperateInfo)

var merchantAppsOperateInfoChangedFuncHandler merchantAppsOperateInfoChangedFunc

func RegisterMerchantAppsOperateInfoChangedHook(fn merchantAppsOperateInfoChangedFunc) {
	merchantAppsOperateInfoChangedFuncHandler = fn
}

type merchantAuthorizedFunc func(r *ghttp.Request, merchant *MerchantInfo) (passportCenterUserApiToken string, err error)

var merchantAuthorizedFuncFuncHandler merchantAuthorizedFunc

func RegisterMerchantAuthorizedFuncHook(fn merchantAuthorizedFunc) {
	merchantAuthorizedFuncFuncHandler = fn
}

func appAuthorize(r *ghttp.Request) {
	// ① 验签
	// https://xxx.com?appkey=326383c947bca5e042807537743f0114a84fb709&handle=goodgoods&lang=zh-hans-cn&timestamp=1646915628733&sign=68126682300b57a855702e2839f91b9eb10d3fa57211415f4eac75b30822e14e

	// 跳转的地址就是  应用设置 -> 基础信息设置 -> 应用地址
	// https://picwish.com/affiliates/shopline/segmentation.html?appkey=fe5730faeaa6de132e9495bdecd8c474b93199f4&handle=wangxu&lang=zh-hans-cn&timestamp=1656468389211&sign=29cfd93aa6de22c7feb780b0475318677bdda4ee62925a1e8552bce17c6ac999
	// https://picwish.com/affiliates/shopline/scale.html

	var req *authorizeRequest
	err := r.Parse(&req)
	if err != nil {
		easyResponse.New(r).ResponseBadRequest(err.Error())
	}

	cfg := getConfigByAppName(req.AppName)

	cleartext := getSortedUrlQuery(r.GetRequestMapStrStr())
	sign := hmacSha256(cleartext, cfg.AppSecret)
	if sign != req.Sign {
		easyResponse.New(r).ResponseUnauthorized("invalid signature", cleartext)
	}

	// ② 申请授权
	// https://goodgoods.myshopline.com/admin/oauth-web/#/oauth/authorize?appKey=326383c947bca5e042807537743f0114a84fb709&responseType=code&scope=read_products&redirectUri=https%3A%2F%2Fwww.baidu.com

	// 注意这个redirectUri必须和shopline填写的一致，否则会报错401不在白名单
	redirectUri := getCallbackUrl(req.AppName)
	// 授权范围，英文逗号拼接 https://developer.myshopline.com/docs/introduce/q1KNP8IY
	scope := "read_products,read_store_information"
	authUrl := fmt.Sprintf(
		"https://%s.myshopline.com/admin/oauth-web/#/oauth/authorize?appKey=%s&responseType=code&scope=%s&redirectUri=%s",
		req.Handle, req.AppKey, scope, url.QueryEscape(redirectUri))

	easyResponse.New(r).RedirectTo(authUrl)
}

func shoplineCallback(r *ghttp.Request) {
	/*
		请求示例：https://05b5-159-138-40-117.ngrok.io/api/affiliates/shopline/apps/segmentation/callbacks?appkey=fe5730faeaa6de132e9495bdecd8c474b93199f4&code=us64325eb097ee37346a87a42ea89a32c9204be624&handle=wangxu&timestamp=1656487556370&sign=4074a2458e8f6f3ca5295dec75c5c6160d310c176c54cde511dfac5554798d20
	*/
	var req *callbackRequest
	err := r.Parse(&req)
	if err != nil {
		easyResponse.New(r).ResponseBadRequest(err.Error())
	}
	cfg := getConfigByAppName(req.AppName)
	// 验签
	cleartext := getSortedUrlQuery(r.GetRequestMapStrStr())
	sign := hmacSha256(cleartext, cfg.AppSecret)
	if sign != req.Sign {
		easyResponse.New(r).ResponseUnauthorized("invalid signature", req)
	}

	// 创建token（在shopline平台创建应用的关联关系）
	tokenResponse := createToken(r.Context(), req)

	ts := gtime.Now().TimestampMicroStr()
	appendQueries := []string{
		"app_name=" + cfg.AppName,
		"timestamp=" + ts,
		"signature=" + hmacSha256(cfg.AppName+ts, cfg.AppSecret),
		"handle=" + req.Handle,
		"languages=" + req.Lang,
	}

	if merchantAuthorizedFuncFuncHandler != nil {
		info, _ := getMerchantInfo(req.Handle, tokenResponse)
		// 从业务侧拿到 用户中心的 api token（自动在passport中心创建一个账户）
		apiToken, e := merchantAuthorizedFuncFuncHandler(r, info)
		if e == nil && apiToken != "" {
			// api token给到前端去用的（因为没有账号和密码）
			appendQueries = append(appendQueries, fmt.Sprintf("api_token=%s", apiToken))
		}
	}

	if tokenResponse != nil {
		// access_token=eyJhbGciOiJIUzUxMiJ9.eyJhcHBLZXkiOiJmZTU3MzBmYWVhYTZkZTEzMmU5NDk1YmRlY2Q4YzQ3NGI5MzE5OWY0Iiwic2VsbGVySWQiOiIyMDAxMjAzOTQ4Iiwic3RvcmVJZCI6IjE2NTYzMjMzNTUzMDYiLCJ2ZXJzaW9uIjoiVjEiLCJkb21haW4iOiJodHRwczovL3NsLW9wZW4tdXMubXlzaG9wbGluZS5jb20iLCJ0aW1lc3RhbXAiOjE2NTY1OTU5NTA3ODcsImlzcyI6Inlzb3VsIiwiZXhwIjoxNjU2NjMxOTUwfQ.YwfySAcZoodq1LY4Ygu3LsAls55BPgoqjGTEWEz8QhDlfPNw8rG5Ong1SjU0icDTrV0QFw3DWP2BEKYRCONmsA
		appendQueries = append(appendQueries, fmt.Sprintf("access_token=%s", tokenResponse.Data.AccessToken))
	}

	redirectUrl := concatUrlQuery(cfg.AppUrl, strings.Join(appendQueries, "&"))
	easyResponse.New(r).RedirectTo(redirectUrl)
}

func shoplineWebhook(r *ghttp.Request) {
	/*
		卸载
		{"appkey":"fe5730faeaa6de132e9495bdecd8c474b93199f4","country_code":"US","created_at":"2022-06-29T18:21:40+08:00","handle":"wangxu","has_extension":false,"merchant_id":"2001203948","name":"网旭测试","operate":"UNINSTALL","store_id":"1656323355306","timestamp":165649810062imezone":"Asia/Shanghai"}

		安装
		{"appkey":"fe5730faeaa6de132e9495bdecd8c474b93199f4","country_code":"US","created_at":"2022-06-29T18:22:40+08:00","handle":"wangxu","has_extension":false,"merchant_id":"2001203948","name":"网旭测试","operate":"INSTALL","store_id":"1656323355306","timestamp":1656498160828,ezone":"Asia/Shanghai"}
	*/

	var op *MerchantAppsOperateInfo
	err := r.Parse(&op)
	if err != nil {
		return
	}
	cfg := getConfigByAppKey(op.AppKey)
	op.AppName = cfg.AppName

	if merchantAppsOperateInfoChangedFuncHandler != nil {
		merchantAppsOperateInfoChangedFuncHandler(r, op)
	}
}

// 申请token (实际上只有首次安装应用授权的时候，才需要 创建token，建立关联关系，后面都不需要了)
func createToken(ctx context.Context, req *callbackRequest) *createTokenResponse {
	// 首次授权才有 Code 参数
	if req.Code == "" {
		return nil
	}

	postData := g.MapStrStr{
		"code": req.Code,
	}

	cfg := getConfigByAppName(req.AppName)
	msg := gconv.String(postData) + req.Timestamp
	sign := hmacSha256(msg, cfg.AppSecret)

	headers := g.MapStrStr{
		"appkey":    req.AppKey,
		"timestamp": req.Timestamp,
		"sign":      sign,
	}
	uri := fmt.Sprintf("https://%s.myshopline.com/admin/oauth/token/create", req.Handle)
	_, body, err := easyHttp.Ctx(ctx).SetHeaders(headers).SetContentTypeJson().Post(uri, postData)

	if err != nil {
		return nil
	}

	/*
		{
			"code": 200,
			"i18nCode": "SUCCESS",
			"message": null,
			"data": {
				"accessToken": "eyJhbGciOiJIUzUxMiJ9.eyJhcHBLZXkiOiJmZTU3MzBmYWVhYTZkZTEzMmU5NDk1YmRlY2Q4YzQ3NGI5MzE5OWY0Iiwic2VsbGVySWQiOiIyMDAxMjAzOTQ4Iiwic3RvcmVJZCI6IjE2NTYzMjMzNTUzMDYiLCJ2ZXJzaW9uIjoiVjIiLCJkb21haW4iOiJodHRwczovL3NsLW9wZW4tdXMubXlzaG9wbGluZS5jb20iLCJ0aW1lc3RhbXAiOjE2NTY0ODg3Mjk0NzgsImlzcyI6Inlzb3VsIiwiZXhwIjoxNjU2NTI0NzI5fQ.Yrdp05i-L_U-HKqmO5PlXfOqi6hL5_jOR790xnBgPqzF7ld_NiRtAwynrLt1FX5sEJn8Ln9v6tqv-AZSLyBqrA",
				"expireTime": "2022-06-29T17:45:29.478+00:00",
				"refreshToken": "06b8f310708309b24cb423745dd70c930932d559",
				"refreshExpireTime": "2099-12-30T16:00:00.000+00:00",
				"scope": "read_products"
		}
		}/*

	*/
	var res *createTokenResponse
	err = gconv.Scan(body, &res)
	if err != nil {
		return nil
	}
	return res
}

// 刷新token（前端调用）
func refreshToken(r *ghttp.Request) {
	/*
		curl --location --request POST 'http://127.0.0.1:8080/api/affiliates/shopline/tokens' \
		--header 'Content-Type: application/json' \
		--data-raw '{
		    "app_name": "segmentation",
		    "timestamp": 1656595194709525,
		    "signature": "a0e01dac14be5f875d5f8ca38a673a6d43a5efbdabb635ec2df8efeb88c10a99",
		    "handle": "wangxu"
		}'
	*/
	var req *refreshTokenRequest
	err := r.Parse(&req)
	if err != nil {
		easyResponse.New(r).ResponseBadRequest(err.Error())
	}
	cfg := getConfigByAppName(req.AppName)
	sign := hmacSha256(req.AppName+req.Timestamp, cfg.AppSecret)
	if sign != req.Signature {
		easyResponse.New(r).ResponseUnauthorized("invalid signature")
	}

	ts := gtime.Now().TimestampMicroStr()

	headers := g.MapStrStr{
		"appkey":    cfg.AppKey,
		"timestamp": ts,
		"sign":      hmacSha256(ts, cfg.AppSecret),
	}
	uri := fmt.Sprintf("https://%s.myshopline.com/admin/oauth/token/refresh", req.Handle)
	_, body, err := easyHttp.Ctx(r.Context()).SetHeaders(headers).SetContentTypeJson().Post(uri)
	if err != nil {
		easyResponse.New(r).ResponseServerError(err.Error())
	}

	var res *createTokenResponse
	err = gconv.Scan(body, &res)
	if err != nil {
		easyResponse.New(r).ResponseServerError(err.Error())
	}

	data := &refreshTokenResponse{
		AccessToken: res.Data.AccessToken,
		Expiration:  res.Data.ExpireTime.Unix(),
	}
	easyResponse.New(r).ResponseOK(data)
}

// 获取店铺信息
func getMerchantInfo(handel string, tokenInfo *createTokenResponse) (*MerchantInfo, error) {
	info := &MerchantInfo{StoreName: handel}
	if tokenInfo == nil {
		return info, errors.New("invalid token info")
	}
	uri := fmt.Sprintf("https://%s.myshopline.com/admin/openapi/v20220601/merchants/shop.json", handel)
	_, body, err := easyHttp.Ctx().SetHeader("Authorization", "Bearer "+tokenInfo.Data.AccessToken).Get(uri)

	if err != nil {
		return info, err
	}

	var m *queryMerchantShopInfoResponse
	err = gconv.Scan(body, &m)
	if err != nil {
		return info, err
	}

	info = &MerchantInfo{
		Email:       m.Data.Email,
		StoreId:     m.Data.ID,
		StoreName:   handel,
		StoreTitle:  m.Data.Name,
		CountryCode: m.Data.LocationCountryCode,
	}
	return info, nil
}
